export namespace BitMath {
    var sqrt = Math.sqrt;
    var pow = Math.pow;
    var cos = Math.cos;
    var atan2 = Math.atan2;
    var sin = Math.sin;
    var abs = Math.abs;
    var exp = Math.exp;
    var PI = Math.PI;
    function degrees(n: number) { return n * (180 / PI); }
    function radians(n:number) { return n * (PI / 180); }

    function hp_f(x:number, y:number) //(7)
    {
        if (x === 0 && y === 0) return 0;
        else {
            var tmphp = degrees(atan2(x, y));
            if (tmphp >= 0) return tmphp
            else return tmphp + 360;
        }
    }

    function dhp_f(C1:number, C2:number, h1p:number, h2p:number) //(10)
    {
        if (C1 * C2 === 0) return 0;
        else if (abs(h2p - h1p) <= 180) return h2p - h1p;
        else if ((h2p - h1p) > 180) return (h2p - h1p) - 360;
        else if ((h2p - h1p) < -180) return (h2p - h1p) + 360;
        else throw (new Error());
    }

    function a_hp_f(C1:number, C2:number, h1p:number, h2p:number) { //(14)
        if (C1 * C2 === 0) return h1p + h2p
        else if (abs(h1p - h2p) <= 180) return (h1p + h2p) / 2.0;
        else if ((abs(h1p - h2p) > 180) && ((h1p + h2p) < 360)) return (h1p + h2p + 360) / 2.0;
        else if ((abs(h1p - h2p) > 180) && ((h1p + h2p) >= 360)) return (h1p + h2p - 360) / 2.0;
        else throw (new Error());
    }

    export class Color8bit_HSV {
        h: number = 0;//0~255 //色度
        s: number = 0;        //彩度
        v: number = 0;        //亮度，這個是非綫性的亮度，取了最大值

        CompareTo(test: Color8bit_HSV): number {
            //权重 色度，彩度，亮度是一样的
            const rightH = 1;
            const rightS = 1;
            const rightV = 1;

            return Math.abs(test.h - this.h) * rightH + Math.abs(test.s - this.s) * rightS + Math.abs(test.v - this.v) * rightV;
        }
    }
    export class Color {
        r: number = 0;//0~1.0
        g: number = 0;
        b: number = 0;
        a: number = 1.0;
        static get White(): Color {
            let w = new Color();
            w.r = w.g = w.b = w.a = 1.0;
            return w;
        }
    }

    const Xn = 0.950456;
    const Yn = 1.0;
    const Zn = 1.088754;

    export class ColorLab {
        l: number = 0;
        a: number = 0;
        b: number = 0;


        FromRGB(r: number, g: number, b: number): void {
            let X = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b;
            let Y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b;
            let Z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b;
            let fX, fY, fZ;
            X /= (Xn);
            Y /= (Yn);
            Z /= (Zn);
            if (Y > 0.008856) {
                fY = Math.pow(Y, 1.0 / 3.0);

            }
            else {
                fY = 7.787 * Y + 16.0 / 116.0;
            }
            if (X > 0.008856)
                fX = Math.pow(X, 1.0 / 3.0);
            else
                fX = 7.787 * X + 16.0 / 116.0;

            if (Z > 0.008856)
                fZ = Math.pow(Z, 1.0 / 3.0);
            else
                fZ = 7.787 * Z + 16.0 / 116.0;
            this.l = (116.0 * Y - 16.0);

            this.a = (fX - fY) * 500;
            this.b = (fY - fZ) * 200;
        }
        static gamma(x: number): number {
            return x > 0.04045 ? Math.pow((x + 0.055) / 1.055, 2.4) : (x / 12.92);
        };

        static QuickDist(a: ColorLab, b: ColorLab): number {


            let L = (a.l - b.l);
            let A = (a.a - b.a);
            let B = (a.b - b.b);
            return Math.sqrt(
                L * L + A * A + B * B
            );
        }
        static ciede2000(c1: ColorLab, c2: ColorLab): number {
            /**
             * Implemented as in "The CIEDE2000 Color-Difference Formula:
             * Implementation Notes, Supplementary Test Data, and Mathematical Observations"
             * by Gaurav Sharma, Wencheng Wu and Edul N. Dalal.
             */

            // Get L,a,b values for color 1
            var L1 = c1.l;
            var a1 = c1.a;
            var b1 = c1.b;

            // Get L,a,b values for color 2
            var L2 = c2.l;
            var a2 = c2.a;
            var b2 = c2.b;

            // Weight factors
            var kL = 1;
            var kC = 1;
            var kH = 1;

            /**
             * Step 1: Calculate C1p, C2p, h1p, h2p
             */
            var C1 = sqrt(pow(a1, 2) + pow(b1, 2)) //(2)
            var C2 = sqrt(pow(a2, 2) + pow(b2, 2)) //(2)

            var a_C1_C2 = (C1 + C2) / 2.0;             //(3)

            var G = 0.5 * (1 - sqrt(pow(a_C1_C2, 7.0) /
                (pow(a_C1_C2, 7.0) + pow(25.0, 7.0)))); //(4)

            var a1p = (1.0 + G) * a1; //(5)
            var a2p = (1.0 + G) * a2; //(5)

            var C1p = sqrt(pow(a1p, 2) + pow(b1, 2)); //(6)
            var C2p = sqrt(pow(a2p, 2) + pow(b2, 2)); //(6)

            var h1p = hp_f(b1, a1p); //(7)
            var h2p = hp_f(b2, a2p); //(7)

            /**
             * Step 2: Calculate dLp, dCp, dHp
             */
            var dLp = L2 - L1; //(8)
            var dCp = C2p - C1p; //(9)

            var dhp = dhp_f(C1, C2, h1p, h2p); //(10)
            var dHp = 2 * sqrt(C1p * C2p) * sin(radians(dhp) / 2.0); //(11)

            /**
             * Step 3: Calculate CIEDE2000 Color-Difference
             */
            var a_L = (L1 + L2) / 2.0; //(12)
            var a_Cp = (C1p + C2p) / 2.0; //(13)

            var a_hp = a_hp_f(C1, C2, h1p, h2p); //(14)
            var T = 1 - 0.17 * cos(radians(a_hp - 30)) + 0.24 * cos(radians(2 * a_hp)) +
                0.32 * cos(radians(3 * a_hp + 6)) - 0.20 * cos(radians(4 * a_hp - 63)); //(15)
            var d_ro = 30 * exp(-(pow((a_hp - 275) / 25, 2))); //(16)
            var RC = sqrt((pow(a_Cp, 7.0)) / (pow(a_Cp, 7.0) + pow(25.0, 7.0)));//(17)
            var SL = 1 + ((0.015 * pow(a_L - 50, 2)) /
                sqrt(20 + pow(a_L - 50, 2.0)));//(18)
            var SC = 1 + 0.045 * a_Cp;//(19)
            var SH = 1 + 0.015 * a_Cp * T;//(20)
            var RT = -2 * RC * sin(radians(2 * d_ro));//(21)
            var dE = sqrt(pow(dLp / (SL * kL), 2) + pow(dCp / (SC * kC), 2) +
                pow(dHp / (SH * kH), 2) + RT * (dCp / (SC * kC)) *
                (dHp / (SH * kH))); //(22)
            return dE;
        }
        
    }
    export class Color8Bit_RGB {
        r: number = 0;
        g: number = 0;
        b: number = 0;
        a: number = 255;
        Copy(src: Color8Bit_RGB) {
            this.r = src.r;
            this.g = src.g;
            this.b = src.b;
            this.a = src.a;
        }
        Mix(left: Color8Bit_RGB, right: Color8Bit_RGB, radio: number) {
            this.r = left.r * (1 - radio) + right.r * radio;
            this.g = left.g * (1 - radio) + right.g * radio;
            this.b = left.b * (1 - radio) + right.b * radio;
            this.a = left.a * (1 - radio) + right.a * radio;
        }
        FromH(h360: number)//输入一个0到360的值
        {
            //h360 = 60
            //sin插值
            let v = (h360 % 120 / 120);
            if (v < 0) v += 1;

            let f = Math.sin(v * Math.PI) * 0.5;
            if (v > 0.5)
                f = 1 - f;
            //直接插值
            //let f = h360 % 120 / 120;
            if (h360 < 120)  //b->r
            {

                if (f <= 0.5) {
                    this.b = 255;
                    this.r = 255 * (f) / (1 - f);
                }
                else {
                    this.r = 255;
                    this.b = 255 * (1 - f) / f;
                }
                this.g = 0;
            }
            else if (h360 < 240) //r->g
            {

                if (f <= 0.5) {
                    this.r = 255;
                    this.g = 255 * f / (1 - f);
                }
                else {
                    this.g = 255
                    this.r = 255 * (1 - f) / f;
                }
                this.b = 0;

            }
            else //g->b
            {

                this.r = 0;
                if (f <= 0.5) {
                    this.g = 255;
                    this.b = 255 * f / (1 - f);
                }
                else {
                    this.b = 255
                    this.g = 255 * (1 - f) / f;
                }
            }
        }
        SetV(v: number) {
            if (v <= 255) {
                this.r *= (v / 255);
                this.g *= (v / 255);
                this.b *= (v / 255);
            }
            else {

                let maxr = v / 255 - 1;
                this.r = this.r * (1 - maxr) + 255 * maxr;
                this.g = this.g * (1 - maxr) + 255 * maxr;
                this.b = this.b * (1 - maxr) + 255 * maxr;
            }
            //if (this.g > 255) this.g = 255;
            //if (this.b > 255) this.b = 255;
        }
        ToWebString(): string {
            let outstr = "";
            let c1 = (this.r | 0).toString(16);
            if (c1.length == 1) c1 = "0" + c1;
            let c2 = (this.g | 0).toString(16);
            if (c2.length == 1) c2 = "0" + c2;
            let c3 = (this.b | 0).toString(16);
            if (c3.length == 1) c3 = "0" + c3;
            return "#" + c1 + c2 + c3;
        }
        ToIntRGBA(): number {

            return ((this.r | 0) << 24) + ((this.g | 0) << 16) + ((this.b | 0) << 8) + 0xff;
        }
        ToInt565(): number {
            let r5 = (this.r / 8) | 0;
            if (r5 > 31) r5 = 31;
            let g6 = (this.g / 4) | 0;
            if (g6 > 63) g6 = 63;
            let b5 = (this.b / 8) | 0;
            if (b5 > 31) b5 = 31;
            return (r5 << 11) + (g6 << 5) + (b5);
        }

        static QuickDist(a: Color8Bit_RGB, b: Color8Bit_RGB): number {
            // double ColourDistance(RGB e1, RGB e2)
            // {
            //   long rmean = ( (long)e1.r + (long)e2.r ) / 2;
            //   long r = (long)e1.r - (long)e2.r;
            //   long g = (long)e1.g - (long)e2.g;
            //   long b = (long)e1.b - (long)e2.b;
            //   return sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8));
            // }
            let rmean = ((a.r + b.r) / 2) | 0;
            let R = (a.r - b.r);
            let G = (a.g - b.g);
            let B = (a.b - b.b);
            return Math.sqrt(
                (((512 + rmean) * (R * R)) >> 8) +
                (4 * (G * G)) +
                ((767 - rmean) * (B * B)) >> 8
            );
        }

    }
    export class Rect {
        constructor(x: number = 0, y: number = 0, w: number = 0, h: number = 0) {
            this.x = x;
            this.y = y;
            this.w = w;
            this.h = h;
        }
        x: number = 0;
        y: number = 0;
        w: number = 0;
        h: number = 0;
    }

    export class LimitRect {
        public xmin: number = 0;
        public ymin: number = 0;
        public xmax: number = 0;
        public ymax: number = 0;
    }

    ///强行規範透明色為0，不支持alpha
    export class Palette {
        colors: BitMath.Color8Bit_RGB[] = [];
        colorsWeb: String[] = [];
        colorsInt: number[] = []
        LoadFromAsepritePal(src: string): boolean {
            while (this.colors.length < 256) {
                let c: BitMath.Color8Bit_RGB = new BitMath.Color8Bit_RGB();
                c.SetV(0);
                this.colors.push(c);
                this.colorsWeb.push("#000");
                this.colorsInt.push(0);
            }
            if (src.indexOf("JASC-PAL\n") != 0)
                return false;
            let lines = src.split("\n");
            let count = parseInt(lines[2]);
            let linecount = count / 16;
            for (var i = 0; i < count; i++) {
                let cs = lines[3 + i].split(" ");
                let y = i % linecount
                let x = (i / linecount) | 0
                this.colors[y * 16 + x].r = parseInt(cs[0]);
                this.colors[y * 16 + x].g = parseInt(cs[1]);
                this.colors[y * 16 + x].b = parseInt(cs[2]);
                this.colorsWeb[y * 16 + x] = this.colors[y * 16 + x].ToWebString();
                if (i > 0) {
                    this.colorsInt[y * 16 + x] = this.colors[y * 16 + x].ToIntRGBA();
                }
            }
            return true;
        }
        LoadFromPSActAndAllHex(u8: Uint8Array): boolean {

            while (this.colors.length < 256) {
                let c: BitMath.Color8Bit_RGB = new BitMath.Color8Bit_RGB();
                c.SetV(0);
                this.colors.push(c);
                this.colorsWeb.push("#000");
                this.colorsInt.push(0);
            }
            let clen = (u8.length / 3) | 0;
            if (u8.length == 772)//psact
                clen = 256;
            else {
                if (clen > 256 || clen == 0)
                    return false;
            }
            for (var i = 0; i < clen; i++) {
                this.colors[i].r = u8[i * 3 + 0];
                this.colors[i].g = u8[i * 3 + 1];
                this.colors[i].b = u8[i * 3 + 2];
                this.colorsWeb[i] = this.colors[i].ToWebString();
                if (i > 0) {
                    this.colorsInt[i] = this.colors[i].ToIntRGBA();
                }
            }
            return true;
        }
        IndexOf(rgb: BitMath.Color8Bit_RGB): number {
            let i = rgb.ToIntRGBA();
            if (i == 0) return 0;
            return this.colorsInt.indexOf(i);
        }
        IndexOf3(r: number, g: number, b: number): number {
            let rgb = new BitMath.Color8Bit_RGB();
            rgb.r = r;
            rgb.g = g;
            rgb.b = b;

            let i = rgb.ToIntRGBA();
            if (i == 0) return 0;
            return this.colorsInt.indexOf(i);
        }
        GetColorRGB(i: number): BitMath.Color8Bit_RGB {
            let c = new BitMath.Color8Bit_RGB();
            if (i <= 0) {
                c.r = c.g = c.b = c.a = 0;
            }
            else {
                c.r = this.colors[i].r | 0;
                c.g = this.colors[i].g | 0;
                c.b = this.colors[i].b | 0;
                c.a = 255;
            }
            return c;
        }
        LoadLut(table: Uint8Array, format: string = "R5B5xG6") {
            this.tableLut = table;
            this.lutFormat = format;
        }
        tableLut: Uint8Array | null = null;
        lutFormat: string = "R5B5xG6";

        Lut_IndexOf(color: BitMath.Color8Bit_RGB): number {
            if (this.tableLut == null)
                throw new Error("fault");
            let i = color.ToInt565();
            if (i < 0 || i > 65535) {
                throw new Error("fault");
            }
            return this.tableLut[i];
        }
        Lut_IndexOf3(r: number, g: number, b: number): number {
            if (this.tableLut == null)
                throw new Error("fault");
            let rgb = new BitMath.Color8Bit_RGB();
            rgb.r = r;
            rgb.g = g;
            rgb.b = b;

            let i = rgb.ToInt565();
            return this.tableLut[i];
        }

        Lut_GetColorRGB(srccolor: BitMath.Color8Bit_RGB): BitMath.Color8Bit_RGB {
            let index = this.Lut_IndexOf(srccolor);
            return this.GetColorRGB(index);
        }
        Init(colors: BitMath.Color8Bit_RGB[]) {

            while (this.colors.length < 256) {
                let c: BitMath.Color8Bit_RGB = new BitMath.Color8Bit_RGB();
                c.SetV(0);
                this.colors.push(c);
                this.colorsWeb.push("#000");
                this.colorsInt.push(0);
            }
            let count = colors.length;
            for (var i = 0; i < count; i++) {
                this.colors[i].r = colors[i].r;
                this.colors[i].g = colors[i].g;
                this.colors[i].b = colors[i].b;
                this.colorsWeb[i] = this.colors[i].ToWebString();
                if (i > 0) {
                    this.colorsInt[i] = this.colors[i].ToIntRGBA();
                }
            }
            return true;
        }
        static CreateDefault(): Palette {
            let pal = new Palette();
            pal.Init(Palette.fillNesColorEX());
            return pal;
        }
        static fillNesColor(): BitMath.Color8Bit_RGB[] {

            let colors: BitMath.Color8Bit_RGB[] = [];
            for (var y = 0; y < 16; y++) {
                for (var x = 0; x < 16; x++) {
                    let c: BitMath.Color8Bit_RGB = new BitMath.Color8Bit_RGB();
                    if (y > 3 || x > 0x0d) {
                        c.r = 0;
                        c.g = 0;
                        c.b = 0;
                    }
                    else if (x == 0 || x == 0x0d) {
                        if (y == 0) c.r = c.g = c.b = 0x00;
                        if (y == 1) c.r = c.g = c.b = 0x60;
                        if (y == 2) c.r = c.g = c.b = 0xa0;
                        if (y == 3) c.r = c.g = c.b = 0xf0;
                    }
                    else {
                        let v = 0;
                        if (y == 0) v = 80;
                        if (y == 1) v = 180;
                        if (y == 2) v = 255 + 110;
                        if (y == 3) v = 255 + 150;
                        c.FromH(((x - 1) * 360 / 12 + 330) % 360);
                        c.SetV(v);
                        //c.FromH(((x - 1) * 360 / 15 + 330) % 360);
                    }
                    colors.push(c);
                }
            }
            return colors;
        }
        static fillNesColorEX(): BitMath.Color8Bit_RGB[] {
            let colors: BitMath.Color8Bit_RGB[] = [];
            for (var y = 0; y < 16; y++) {
                for (var x = 0; x < 16; x++) {
                    let c: BitMath.Color8Bit_RGB = new BitMath.Color8Bit_RGB();
                    if (x == 0) {
                        c.r = 255;
                        c.g = 255;
                        c.b = 255;
                        c.SetV((y - 1) * 16);
                    }
                    else {
                        let line = (y / 8) | 0;
                        let v = (y % 8) * 55 + 50 //;; 0 1 2 3 4   0.2  0.5  
                        let h = ((x - 1) + line * 15) //0~45
                        c.FromH((h * 360 / 45) % 360);
                        c.SetV(v);
                    }
                    //c.FromH(((x - 1) * 360 / 15 + 330) % 360);
                    colors.push(c);

                }
            }
            return colors;
        }
    }

}